package com.jprocms.module.cms.service.staticpage;

import com.jpro.module.infra.api.file.FileApi;
import com.jpro.module.infra.api.file.dto.StoreHtmlReqDTO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelDO;
import com.jprocms.module.cms.dal.dataobject.content.ContentDO;
import com.jprocms.module.cms.dal.dataobject.content.ContentExtDO;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.dal.dataobject.tplinclude.TplIncludeDO;
import com.jprocms.module.cms.dal.mysql.channel.ChannelExtMapper;
import com.jprocms.module.cms.dal.mysql.content.ContentExtMapper;
import com.jprocms.module.cms.dal.mysql.site.SiteMapper;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.FrontContextUtil;
import com.jprocms.module.cms.web.Directives;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerProperties;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jprocms.module.cms.constant.CmsSysConstants.SUFFIX;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.CONTENT_STATIC_ERROR;
import static com.jprocms.module.cms.util.FrontContextUtil.PAGE_SIZE;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 静态页 Service
 *
 * @author jprocms
 */
@Slf4j
@Service
public class StaticHtmlService {
    private final SiteMapper siteMapper;
    private final ContentExtMapper contentExtMapper;
    private final ChannelExtMapper channelExtMapper;
    private final Configuration configuration;
    private final FreeMarkerProperties properties;
    private final FileApi fileApi;

    public StaticHtmlService(SiteMapper siteMapper, ContentExtMapper contentExtMapper, ChannelExtMapper channelExtMapper,
                             FileApi fileApi, Configuration configuration, FreeMarkerProperties properties) {
        this.siteMapper = siteMapper;
        this.contentExtMapper = contentExtMapper;
        this.channelExtMapper = channelExtMapper;
        this.configuration = configuration;
        this.properties = properties;
        this.fileApi = fileApi;
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateIndexHtml(SiteDO site) {
        deleteIndexHtml(site);
        if (!site.getSiteConfig().getStaticPage().getOpenSiteStaticPage()) {
            return;
        }
        Map<String, Object> dataModel = new HashMap<>(16);
        FrontContextUtil.setDate(dataModel, site, site.getUrl(), 1, null);
        dataModel.put(PAGE_SIZE, site.getSiteConfig().getStaticPage().getListMaxPageSize());
        dataModel.put("isIndex", true);
        try {
            CmsContextUtil.setPc(true);
            String filename = site.getPcStaticIndexFilePath();
            final Template template = resolveTemplate(site.getTpl());
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            template.process(dataModel, new OutputStreamWriter(os, UTF_8));
            fileApi.storeHtml(new StoreHtmlReqDTO(filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue(), new String(os.toByteArray())));
            site.setStaticHtml(filename);
            log.error("生成站点首页静态化,{}", filename);
            LambdaUpdateWrapper<SiteDO> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(SiteDO::getId, site.getId());
            updateWrapper.set(SiteDO::getStaticHtml, filename);
            if (site.hasH5DifferentSolution()) {
                CmsContextUtil.setPc(false);
                String h5filename = site.getH5StaticIndexFilePath();
                ByteArrayOutputStream osh5 = new ByteArrayOutputStream();
                final Template h5template = resolveTemplate(site.getTpl());
                h5template.process(dataModel, new OutputStreamWriter(osh5, UTF_8));
                fileApi.storeHtml(new StoreHtmlReqDTO(h5filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue(), new String(osh5.toByteArray())));
                site.setH5StaticHtml(filename);
                updateWrapper.set(SiteDO::getH5StaticHtml, filename);
            }
            siteMapper.update(new SiteDO(), updateWrapper);
        } catch (Exception e) {
            log.error("生成站点首页静态化失败,{}", e.getMessage());
            e.printStackTrace();
            throw exception(CONTENT_STATIC_ERROR);
        } finally {
            CmsContextUtil.clearPc();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteIndexHtml(SiteDO site) {
        String staticHtml = site.getStaticHtml();
        try {
            LambdaUpdateWrapper<SiteDO> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(SiteDO::getId, site.getId());
            if (staticHtml != null) {
                fileApi.deleteFile(staticHtml, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue());
                site.setStaticHtml(null);
                updateWrapper.set(SiteDO::getStaticHtml, null);
                log.error("删除站点首页静态化文件");
            }
            String filename = site.getH5StaticHtml();
            if (filename != null) {
                fileApi.deleteFile(filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue());
                site.setH5StaticHtml(null);
                updateWrapper.set(SiteDO::getH5StaticHtml, null);
                log.error("删除站点h5首页静态化文件");
            }
            siteMapper.update(new SiteDO(), updateWrapper);
        } catch (Exception e) {
            log.error("删除站点首页静态化文件失败{}", e.getMessage());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateContentHtml(ContentDO contentDO) {
        try {
            deleteContentHtml(contentDO);
            //非发布状态不用静态化 跳转连接不用静态化  或者是所在栏目未开启静态化 或者是站点未开启静态化
            if (!contentDO.isPublish() || !StringUtils.isBlank(contentDO.getLinkUrl()) || !contentDO.getSite().getOpenSiteStaticPage()) {
                return;
            }
            SiteDO site = contentDO.getSite();
            Map<String, Object> dataModel = new HashMap<>(16);
            dataModel.put("content", contentDO);
            dataModel.put("channel", contentDO.getChannel());
            dataModel.put("isIndex", false);
            int page = 1;
            FrontContextUtil.setDate(dataModel, site, contentDO.getUrl(page), page, contentDO);
            ContentExtDO ext = contentDO.getExt();
            CmsContextUtil.setPc(true);
            String filename = contentDO.getPcStaticPath(page);
            final Template template = resolveTemplate(contentDO.getTpl());
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            template.process(dataModel, new OutputStreamWriter(os, UTF_8));
            fileApi.storeHtml(new StoreHtmlReqDTO(filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue(), new String(os.toByteArray())));
            ext.setStaticFile(filename);
            log.error("生成内容静态化,title:{} 文件 {}", contentDO.getTitle(), filename);
            if (site.hasH5DifferentSolution()) {
                CmsContextUtil.setPc(false);
                String h5filename = contentDO.getH5StaticPath(page);
                final Template h5template = resolveTemplate(contentDO.getTpl());
                ByteArrayOutputStream h5os = new ByteArrayOutputStream();
                h5template.process(dataModel, new OutputStreamWriter(h5os, UTF_8));
                fileApi.storeHtml(new StoreHtmlReqDTO(filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue(), new String(h5os.toByteArray())));
                ext.setH5StaticFile(h5filename);
                log.error("生成内容静态化手机端页面,title:{} 文件 {}", contentDO.getTitle(), h5filename);
            }
            contentExtMapper.updateById(ext);
        } catch (Exception e) {
            log.error("生成内容静态化失败{}", e.getMessage());
            throw exception(CONTENT_STATIC_ERROR);
        } finally {
            CmsContextUtil.clearPc();
        }
    }

    public void deleteContentHtml(ContentDO contentDO) throws Exception {
        String staticHtml = contentDO.getStaticFile();
        if (staticHtml != null) {
            fileApi.deleteFile(staticHtml, contentDO.getSite().getGlobalConfigDO().getHtmlStoreConfigId().longValue());
            contentDO.setStaticFile(null);
            log.error("删除栏目静态化,title:{}", contentDO.getTitle());
        }
        String filename = contentDO.getH5StaticFile();
        if (filename != null) {
            fileApi.deleteFile(filename, contentDO.getSite().getGlobalConfigDO().getHtmlStoreConfigId().longValue());
            contentDO.setH5StaticFile(null);
            log.error("删除栏目手机端静态化,title:{} 文件 {}", contentDO.getTitle());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateChannelHtml(ChannelDO channel) {
        try {
            deleteChannelHtml(channel);
            // 跳转连接不用静态化  或者是所在栏目未开启静态化 或者是站点未开启静态化
            if (StringUtils.isNotBlank(channel.getLinkUrl()) || !channel.getSite().getOpenSiteStaticPage()) {
                return;
            }
            CmsContextUtil.setPc(true);
            doCreateChannelHtml(channel);
            // 手机端模板和PC端模板不一样，则再独立生成手机端页面
            if (channel.getSite().hasH5DifferentSolution()) {
                CmsContextUtil.setPc(false);
                doCreateChannelHtml(channel);
            }
            channelExtMapper.updateById(channel.getChannelExt());
        } catch (Exception e) {
            log.error("生成内容静态化失败{}", e.getMessage());
            throw exception(CONTENT_STATIC_ERROR);
        } finally {
            CmsContextUtil.clearPc();
        }
    }

    private void doCreateChannelHtml(ChannelDO channel) throws Exception {
        SiteDO site = channel.getSite();
        Map<String, Object> dataModel = new HashMap<>(16);
        dataModel.put("channel", channel);
        dataModel.put("isIndex", false);
        dataModel.put(PAGE_SIZE, channel.getChannelExt().getPageSize());
        int page = 1;
        Long totalPages = 1L;
        int maxPages = site.getSiteConfig().getStaticPage().getListMaxPageSize();
        log.error("生成栏目静态化,name:{}", channel.getName());
        for (; page <= totalPages && page <= maxPages; page += 1) {
            FrontContextUtil.setDate(dataModel, site, channel.getUrl(page), page, channel);
            String filename = CmsContextUtil.isPc() ? channel.getPcStaticPath(page) : channel.getH5StaticPath(page);
            // ContentPage 标签会设置 totalPages。先清除，执行模板后再获取，以免获取到无效数据。
            Directives.clearTotalPages();
            final Template template = resolveTemplate(channel.getTpl());
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            template.process(dataModel, new OutputStreamWriter(os, UTF_8));
            fileApi.storeHtml(new StoreHtmlReqDTO(filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue(), new String(os.toByteArray())));
            totalPages = Directives.getTotalPages();
            if (page == 1) {
                if (CmsContextUtil.isPc()) {
                    channel.setStaticFile(filename);
                } else {
                    channel.setH5StaticFile(filename);
                }
            }
        }
    }

    public void deleteChannelHtml(ChannelDO channel) throws Exception {
        String staticHtml = channel.getStaticFile();
        if (staticHtml != null) {
            fileApi.deleteFile(staticHtml, channel.getSite().getGlobalConfigDO().getHtmlStoreConfigId().longValue());
            deleteHtmlList(channel.getSite().getGlobalConfigDO().getHtmlStoreConfigId().longValue(), staticHtml);
            channel.setStaticFile(null);
            log.error("删除栏目静态化文件,name:{}", channel.getName());
        }
        String filename = channel.getH5StaticFile();
        if (filename != null) {
            deleteHtmlList(channel.getSite().getGlobalConfigDO().getHtmlStoreConfigId().longValue(), filename);
            channel.setH5StaticFile(null);
            log.error("删除栏目手机端静态化文件,name:{}", channel.getName());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateIncludePage(TplIncludeDO tplIncludeDO) {
        try {
            CmsContextUtil.setPc(true);
            SiteDO site = siteMapper.selectById(tplIncludeDO.getSiteId());
            updateIncludeHtml(tplIncludeDO, site);
        } catch (Exception e) {
            log.error("生成内容静态化失败{}", e.getMessage());
            throw exception(CONTENT_STATIC_ERROR);
        } finally {
            CmsContextUtil.clearPc();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateIncludeHtml(TplIncludeDO tplIncludeDO, SiteDO site) {
        if (!site.getSiteConfig().getStaticPage().getOpenSiteStaticPage()) {
            return;
        }
        Map<String, Object> dataModel = new HashMap<>(16);
        FrontContextUtil.setDate(dataModel, site, site.getUrl(), 1, null);
        dataModel.put(PAGE_SIZE, site.getSiteConfig().getStaticPage().getListMaxPageSize());
        dataModel.put("isIndex", false);
        try {
            CmsContextUtil.setPc(true);
            String filename = tplIncludeDO.getStaticFilePath(site);
            final Template template = resolveTemplate(tplIncludeDO.getTplPathForStatic());
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            template.process(dataModel, new OutputStreamWriter(os, UTF_8));
            fileApi.storeHtml(new StoreHtmlReqDTO(filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue(), new String(os.toByteArray())));
            log.error("生成站点包含页面静态化,{}", filename);
            if (site.hasH5DifferentSolution()) {
                CmsContextUtil.setPc(false);
                String h5filename = tplIncludeDO.getStaticFilePath(site);
                ByteArrayOutputStream osh5 = new ByteArrayOutputStream();
                final Template h5template = resolveTemplate(tplIncludeDO.getTplPathForStatic());
                h5template.process(dataModel, new OutputStreamWriter(osh5, UTF_8));
                fileApi.storeHtml(new StoreHtmlReqDTO(h5filename, site.getGlobalConfigDO().getHtmlStoreConfigId().longValue(), new String(osh5.toByteArray())));
            }
        } catch (Exception e) {
            log.error("生成站点包含页面静态化失败,{}", e.getMessage());
            e.printStackTrace();
            throw exception(CONTENT_STATIC_ERROR);
        } finally {
            CmsContextUtil.clearPc();
        }
    }

    private void deleteHtmlList(Long htmlStoreConfigId, @Nullable String filename) throws Exception {
        if (StringUtils.isBlank(filename)) {
            return;
        }
        if (!filename.endsWith(SUFFIX)) {
            throw new IllegalArgumentException("filename must ends with '.html': " + filename);
        }
        String baseFilename = filename.substring(0, filename.length() - SUFFIX.length());
        // 第一次删除无分页的索引页，第二次要从第二页开始删除，所以page=2
        for (int page = 2; fileApi.deleteFile(filename, htmlStoreConfigId); page += 1) {
            filename = baseFilename + "_" + page + SUFFIX;
        }
    }

    private Template resolveTemplate(String template) throws IOException {
        return configuration.getTemplate(template + properties.getSuffix());
    }
}
